//Android BFF Loader/Renderer v1.07
//Copyright 2011 Karl Walsh
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.

package com.digitalbonestudios.slugpug.tv;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;

import android.content.res.AssetManager;
import android.content.Context;

public class TexFont {
	Context mContext;
	float xScale, yScale;
	int fntTexWidth, fntTexHeight;
	int fntCellWidth, fntCellHeight;
	int BPP, firstCharOffset, colCount;
	int[] charWidth;
	int texID;
	float redVal, greenVal, blueVal;
	int curX, curY;
	int UVarray[];

	public TexFont(Context context, GL10 gl) {
		// Get handle on assets
		mContext = context;

		// Initialise parameters
		redVal = greenVal = blueVal = 1.0f;
		curX = curY = 0;

		// Set scale to neutral
		xScale = yScale = 1.0f;

		// Array to hold character width data
		charWidth = new int[256];

		// UV Array for crop rectangle
		UVarray = new int[4];

		// Generate GL texture ID
		int temp[] = new int[1];
		gl.glGenTextures(1, temp, 0);
		texID = temp[0];

		gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);

		// Set texture parameters
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
				GL10.GL_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
				GL10.GL_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
				GL10.GL_CLAMP_TO_EDGE);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
				GL10.GL_CLAMP_TO_EDGE);
	}

	// BFF files should be placed in the assets folder to be picked up by this
	// function
	public Boolean LoadFontAlt(String fontName, GL10 gl) throws IOException {
		int bytesRead;

		// Array for file header
		byte[] head = new byte[20];

		try {
			AssetManager am = mContext.getAssets();
			InputStream uStream = am.open(fontName, AssetManager.ACCESS_BUFFER);

			// Read header
			bytesRead = uStream.read(head, 0, 20);

			// Check header size is correct
			if (bytesRead < 20) {
				throw new IOException("Header read failed");
			}

			ByteBuffer headBuf = ByteBuffer.wrap(head);

			// Check header
			int h0 = getUnsignedByteVal(headBuf.get());
			int h1 = getUnsignedByteVal(headBuf.get());

			// Check header signature
			if (h0 != 0xBF || h1 != 0xF2) {
				uStream.close();
				throw new IOException("Bad header signature");
			}

			// Get image width and height
			fntTexWidth = flipEndian(headBuf.getInt());
			fntTexHeight = flipEndian(headBuf.getInt());

			// Get cell dimensions
			fntCellWidth = flipEndian(headBuf.getInt());
			fntCellHeight = flipEndian(headBuf.getInt());

			// Sanity check (prevent divide by zero)
			if (fntCellWidth <= 0 || fntCellHeight <= 0) {
				throw new IOException("Invalid header content");
			}

			// Pre-calculate column count
			colCount = fntTexWidth / fntCellWidth;

			// Get colour depth
			BPP = getUnsignedByteVal(headBuf.get());

			// Get base offset
			firstCharOffset = getUnsignedByteVal(headBuf.get());

			// Read width information
			for (int wLoop = 0; wLoop < 256; ++wLoop) {
				charWidth[wLoop] = uStream.read(); // read() returns an unsigned
													// byte, unlike the
													// overloaded versions. :s
			}

			// Get bitmap
			int bitLen = (fntTexHeight * fntTexWidth) * (BPP / 8);
			byte bits[] = new byte[bitLen];

			uStream.read(bits, 0, bitLen);

			uStream.close();

			// Flip image scanlines and wrap in Bytebuffer for glTexImage2D
			ByteBuffer pix = ByteBuffer.allocate(bits.length);
			int lineLen = fntTexWidth * (BPP / 8);

			for (int lines = fntTexHeight - 1; lines > 0; --lines) {
				pix.put(bits, lines * lineLen, lineLen);
			}

			// Place bitmap in texture
			gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);

			switch (BPP) {
			case 8: // Alpha channel info only
				gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_ALPHA,
						fntTexWidth, fntTexHeight, 0, GL10.GL_ALPHA,
						GL10.GL_UNSIGNED_BYTE, pix);
				break;

			case 24: // RGB Texture
				gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGB,
						fntTexWidth, fntTexHeight, 0, GL10.GL_RGB,
						GL10.GL_UNSIGNED_BYTE, pix);
				break;

			case 32: // RGBA Texture
				gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA,
						fntTexWidth, fntTexHeight, 0, GL10.GL_RGBA,
						GL10.GL_UNSIGNED_BYTE, pix);
				break;
			}

		} catch (java.io.EOFException e) {
			throw e;
		} catch (java.io.IOException e) {
			throw e;
		}

		return true;
	}

	// BFF files should be placed in the assets folder to be picked up by this
	// function
	public Boolean LoadFont(String fontName, GL10 gl) throws IOException {
		AssetManager am = mContext.getAssets();
		InputStream uStream = am.open(fontName, AssetManager.ACCESS_BUFFER);
		DataInputStream in = new DataInputStream(uStream); // Get around Java's
															// stupid unsigned
															// bytes

		// Check header - should read 0xBF 0xF2 (BFF2)
		int h0 = in.readUnsignedByte();
		int h1 = in.readUnsignedByte();

		if (h0 != 0xBF || h1 != 0xF2) {
			in.close();
			return false;
		}

		// Get image dimensions
		fntTexWidth = flipEndian(in.readInt());
		fntTexHeight = flipEndian(in.readInt());

		// Get cell dimensions
		fntCellWidth = flipEndian(in.readInt());
		fntCellHeight = flipEndian(in.readInt());

		// Sanity check (prevent divide by zero)
		if (fntCellWidth <= 0 || fntCellHeight <= 0) {
			throw new IOException("Invalid header content");
		}

		// Pre-calculate column count
		colCount = fntTexWidth / fntCellWidth;

		// Get colour depth
		BPP = in.readUnsignedByte();

		// Get base offset
		firstCharOffset = in.readUnsignedByte();

		// Read width information
		for (int wLoop = 0; wLoop < 256; ++wLoop) {
			charWidth[wLoop] = in.readUnsignedByte();
		}

		// Get bitmap
		int bitLen = (fntTexHeight * fntTexWidth) * (BPP / 8);
		byte bits[] = new byte[bitLen];

		in.read(bits, 0, bitLen);

		in.close();

		// Flip bits and wrap in Bytebuffer for glTexImage2D
		ByteBuffer pix = ByteBuffer.allocate(bits.length);
		int lineLen = fntTexWidth * (BPP / 8);

		for (int lines = fntTexHeight - 1; lines > 0; --lines) {
			pix.put(bits, lines * lineLen, lineLen);
		}

		// Place bitmap in texture
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);

		switch (BPP) {
		case 8: // Alpha channel info only
			gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_ALPHA, fntTexWidth,
					fntTexHeight, 0, GL10.GL_ALPHA, GL10.GL_UNSIGNED_BYTE, pix);
			break;

		case 24: // RGB Texture
			gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGB, fntTexWidth,
					fntTexHeight, 0, GL10.GL_RGB, GL10.GL_UNSIGNED_BYTE, pix);
			break;

		case 32: // RGBA Texture
			gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, fntTexWidth,
					fntTexHeight, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pix);
			break;
		}

		return true;
	}

	// Flip endian-ness of a 32 bit integer value. (Stupid Java, no cake for
	// you)
	int flipEndian(int val) {
		return (val >>> 24) | (val << 24) | ((val << 8) & 0x00FF0000)
				| ((val >> 8) & 0x0000FF00);
	}

	// Bodge to get unsigned byte values
	int getUnsignedByteVal(byte val) {
		if (val < 0)
			return 256 + val;
		else
			return val;
	}

	// Set a general scale for text
	public void SetScale(float scaleVal) {
		xScale = yScale = scaleVal;
	}

	// Set text width and height scale independently
	public void SetScale(float xScaleVal, float yScaleVal) {
		xScale = xScaleVal;
		yScale = yScaleVal;
	}

	// Set colour for text quads.
	public void SetPolyColor(float red, float green, float blue) {
		redVal = red;
		greenVal = green;
		blueVal = blue;
	}

	// Set cursor position
	public void SetCursor(int x, int y) {
		curX = x;
		curY = y;
	}

	// Print a line of text at current cursor position
	public void Print(GL10 gl, String text) {
		float xPos = curX;

		// Calculate quad size from scaling factors
		float cellWidth = fntCellWidth * xScale;
		float cellHeight = fntCellHeight * yScale;

		// The height and width values in the crop rect never change
		UVarray[2] = fntCellWidth;
		UVarray[3] = fntCellHeight;

		// Set up GL for rendering the text
		gl.glColor4f(redVal, greenVal, blueVal, 1.0f);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);

		// Loop through each character of the string
		for (int index = 0; index != text.length(); ++index) {
			// Calculate glyph position within texture
			int glyph = (int) text.charAt(index) - firstCharOffset;
			int col = glyph % colCount;
			int row = (glyph / colCount) + 1;

			// Update the crop rect
			UVarray[0] = col * fntCellWidth;
			UVarray[1] = fntTexHeight - (row * fntCellHeight);

			// Set crop area
			((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,
					GL11Ext.GL_TEXTURE_CROP_RECT_OES, UVarray, 0);
			// Draw texture
			((GL11Ext) gl).glDrawTexfOES(xPos, (float) curY, 0.0f, cellWidth,
					cellHeight);

			// Add character width to offset for next glyph
			xPos += (xScale * charWidth[glyph + firstCharOffset]);
		}

		// Update cursor position
		curX = (int) xPos;
	}

	// Print a line of text to screen at specified co-ords
	public void PrintAt(GL10 gl, String text, int x, int y) {
		int glyph, col, row;
		float xPos = x;

		// Calculate quad size from scaling factors
		float cellWidth = fntCellWidth * xScale;
		float cellHeight = fntCellHeight * yScale;

		// The height and width values in the crop rect never change
		UVarray[2] = fntCellWidth;
		UVarray[3] = fntCellHeight;

		// Set up GL for rendering the text
		gl.glColor4f(redVal, greenVal, blueVal, 1.0f);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);

		// Loop through each character of the string
		for (int index = 0; index != text.length(); ++index) {
			// Calculate glyph position within texture
			glyph = (int) text.charAt(index) - firstCharOffset;
			col = glyph % colCount;
			row = (glyph / colCount) + 1;

			// Update the crop rect
			UVarray[0] = col * fntCellWidth;
			UVarray[1] = fntTexHeight - (row * fntCellHeight);

			// Set crop area
			((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,
					GL11Ext.GL_TEXTURE_CROP_RECT_OES, UVarray, 0);
			// Draw texture
			((GL11Ext) gl).glDrawTexfOES(xPos, (float) y, 0.0f, cellWidth,
					cellHeight);

			// Add character width to offset for next glyph
			xPos += (xScale * charWidth[glyph + firstCharOffset]);
		}

		// Update cursor position
		curX = (int) xPos;
	}

	// Return the length (pixels) of a string
	public int GetTextLength(String text) {
		float len = 0.0f;

		for (int index = 0; index != text.length(); ++index) {
			len += (xScale * charWidth[(int) text.charAt(index)]);
		}

		return (int) len;
	}

	// Return the height of text
	public int GetTextHeight() {
		return (int) (fntCellHeight * yScale);
	}

}
